/**
 * Copyright (c) 2006-2016, JGraph Ltd
 * Copyright (c) 2006-2016, Gaudenz Alder
 */
/**
 * Class: mxRubberband
 *
 * Event handler that selects rectangular regions. This is not built-into
 * <mxGraph>. To enable rubberband selection in a graph, use the following code.
 *
 * Example:
 *
 * (code)
 * var rubberband = new mxRubberband(graph);
 * (end)
 *
 * Constructor: mxRubberband
 *
 * Constructs an event handler that selects rectangular regions in the graph
 * using rubberband selection.
 */
function mxRubberband(graph) {
    if (graph != null) {
        this.graph = graph;
        this.graph.addMouseListener(this);

        // Handles force rubberband event
        this.forceRubberbandHandler = mxUtils.bind(this, function (sender, evt) {
            var evtName = evt.getProperty('eventName');
            var me = evt.getProperty('event');

            if (evtName == mxEvent.MOUSE_DOWN && this.isForceRubberbandEvent(me)) {
                var offset = mxUtils.getOffset(this.graph.container);
                var origin = mxUtils.getScrollOrigin(this.graph.container);
                origin.x -= offset.x;
                origin.y -= offset.y;
                this.start(me.getX() + origin.x, me.getY() + origin.y);
                me.consume(false);
            }
        });

        this.graph.addListener(mxEvent.FIRE_MOUSE_EVENT, this.forceRubberbandHandler);

        // Repaints the marquee after autoscroll
        this.panHandler = mxUtils.bind(this, function () {
            this.repaint();
        });

        this.graph.addListener(mxEvent.PAN, this.panHandler);

        // Does not show menu if any touch gestures take place after the trigger
        this.gestureHandler = mxUtils.bind(this, function (sender, eo) {
            if (this.first != null) {
                this.reset();
            }
        });

        this.graph.addListener(mxEvent.GESTURE, this.gestureHandler);

        // Automatic deallocation of memory
        if (mxClient.IS_IE) {
            mxEvent.addListener(window, 'unload',
                mxUtils.bind(this, function () {
                    this.destroy();
                })
            );
        }
    }
};

/**
 * Variable: defaultOpacity
 *
 * Specifies the default opacity to be used for the rubberband div. Default
 * is 20.
 */
mxRubberband.prototype.defaultOpacity = 20;

/**
 * Variable: enabled
 *
 * Specifies if events are handled. Default is true.
 */
mxRubberband.prototype.enabled = true;

/**
 * Variable: div
 *
 * Holds the DIV element which is currently visible.
 */
mxRubberband.prototype.div = null;

/**
 * Variable: sharedDiv
 *
 * Holds the DIV element which is used to display the rubberband.
 */
mxRubberband.prototype.sharedDiv = null;

/**
 * Variable: currentX
 *
 * Holds the value of the x argument in the last call to <update>.
 */
mxRubberband.prototype.currentX = 0;

/**
 * Variable: currentY
 *
 * Holds the value of the y argument in the last call to <update>.
 */
mxRubberband.prototype.currentY = 0;

/**
 * Variable: fadeOut
 *
 * Optional fade out effect. Default is false.
 */
mxRubberband.prototype.fadeOut = false;

/**
 * Function: isEnabled
 *
 * Returns true if events are handled. This implementation returns
 * <enabled>.
 */
mxRubberband.prototype.isEnabled = function () {
    return this.enabled;
};

/**
 * Function: setEnabled
 *
 * Enables or disables event handling. This implementation updates
 * <enabled>.
 */
mxRubberband.prototype.setEnabled = function (enabled) {
    this.enabled = enabled;
};

/**
 * Function: isForceRubberbandEvent
 *
 * Returns true if the given <mxMouseEvent> should start rubberband selection.
 * This implementation returns true if the alt key is pressed.
 */
mxRubberband.prototype.isForceRubberbandEvent = function (me) {
    return mxEvent.isAltDown(me.getEvent());
};

/**
 * Function: mouseDown
 *
 * Handles the event by initiating a rubberband selection. By consuming the
 * event all subsequent events of the gesture are redirected to this
 * handler.
 */
mxRubberband.prototype.mouseDown = function (sender, me) {
    if (!me.isConsumed() && this.isEnabled() && this.graph.isEnabled() &&
        me.getState() == null && !mxEvent.isMultiTouchEvent(me.getEvent())) {
        var offset = mxUtils.getOffset(this.graph.container);
        var origin = mxUtils.getScrollOrigin(this.graph.container);
        origin.x -= offset.x;
        origin.y -= offset.y;
        this.start(me.getX() + origin.x, me.getY() + origin.y);

        // Does not prevent the default for this event so that the
        // event processing chain is still executed even if we start
        // rubberbanding. This is required eg. in ExtJs to hide the
        // current context menu. In mouseMove we'll make sure we're
        // not selecting anything while we're rubberbanding.
        me.consume(false);
    }
};

/**
 * Function: start
 *
 * Sets the start point for the rubberband selection.
 */
mxRubberband.prototype.start = function (x, y) {
    this.first = new mxPoint(x, y);

    var container = this.graph.container;

    function createMouseEvent(evt) {
        var me = new mxMouseEvent(evt);
        var pt = mxUtils.convertPoint(container, me.getX(), me.getY());

        me.graphX = pt.x;
        me.graphY = pt.y;

        return me;
    };

    this.dragHandler = mxUtils.bind(this, function (evt) {
        this.mouseMove(this.graph, createMouseEvent(evt));
    });

    this.dropHandler = mxUtils.bind(this, function (evt) {
        this.mouseUp(this.graph, createMouseEvent(evt));
    });

    // Workaround for rubberband stopping if the mouse leaves the container in Firefox
    if (mxClient.IS_FF) {
        mxEvent.addGestureListeners(document, null, this.dragHandler, this.dropHandler);
    }
};

/**
 * Function: mouseMove
 *
 * Handles the event by updating therubberband selection.
 */
mxRubberband.prototype.mouseMove = function (sender, me) {
    if (!me.isConsumed() && this.first != null) {
        var origin = mxUtils.getScrollOrigin(this.graph.container);
        var offset = mxUtils.getOffset(this.graph.container);
        origin.x -= offset.x;
        origin.y -= offset.y;
        var x = me.getX() + origin.x;
        var y = me.getY() + origin.y;
        var dx = this.first.x - x;
        var dy = this.first.y - y;
        var tol = this.graph.tolerance;

        if (this.div != null || Math.abs(dx) > tol || Math.abs(dy) > tol) {
            if (this.div == null) {
                this.div = this.createShape();
            }

            // Clears selection while rubberbanding. This is required because
            // the event is not consumed in mouseDown.
            mxUtils.clearSelection();

            this.update(x, y);
            me.consume();
        }
    }
};

/**
 * Function: createShape
 *
 * Creates the rubberband selection shape.
 */
mxRubberband.prototype.createShape = function () {
    if (this.sharedDiv == null) {
        this.sharedDiv = document.createElement('div');
        this.sharedDiv.className = 'mxRubberband';
        mxUtils.setOpacity(this.sharedDiv, this.defaultOpacity);
    }

    this.graph.container.appendChild(this.sharedDiv);
    var result = this.sharedDiv;

    if (mxClient.IS_SVG && (!mxClient.IS_IE || document.documentMode >= 10) && this.fadeOut) {
        this.sharedDiv = null;
    }

    return result;
};

/**
 * Function: isActive
 *
 * Returns true if this handler is active.
 */
mxRubberband.prototype.isActive = function (sender, me) {
    return this.div != null && this.div.style.display != 'none';
};

/**
 * Function: mouseUp
 *
 * Handles the event by selecting the region of the rubberband using
 * <mxGraph.selectRegion>.
 */
mxRubberband.prototype.mouseUp = function (sender, me) {
    var active = this.isActive();
    this.reset();

    if (active) {
        this.execute(me.getEvent());
        me.consume();
    }
};

/**
 * Function: execute
 *
 * Resets the state of this handler and selects the current region
 * for the given event.
 */
mxRubberband.prototype.execute = function (evt) {
    var rect = new mxRectangle(this.x, this.y, this.width, this.height);
    this.graph.selectRegion(rect, evt);
};

/**
 * Function: reset
 *
 * Resets the state of the rubberband selection.
 */
mxRubberband.prototype.reset = function () {
    if (this.div != null) {
        if (mxClient.IS_SVG && (!mxClient.IS_IE || document.documentMode >= 10) && this.fadeOut) {
            var temp = this.div;
            mxUtils.setPrefixedStyle(temp.style, 'transition', 'all 0.2s linear');
            temp.style.pointerEvents = 'none';
            temp.style.opacity = 0;

            window.setTimeout(function () {
                temp.parentNode.removeChild(temp);
            }, 200);
        }
        else {
            this.div.parentNode.removeChild(this.div);
        }
    }

    mxEvent.removeGestureListeners(document, null, this.dragHandler, this.dropHandler);
    this.dragHandler = null;
    this.dropHandler = null;

    this.currentX = 0;
    this.currentY = 0;
    this.first = null;
    this.div = null;
};

/**
 * Function: update
 *
 * Sets <currentX> and <currentY> and calls <repaint>.
 */
mxRubberband.prototype.update = function (x, y) {
    this.currentX = x;
    this.currentY = y;

    this.repaint();
};

/**
 * Function: repaint
 *
 * Computes the bounding box and updates the style of the <div>.
 */
mxRubberband.prototype.repaint = function () {
    if (this.div != null) {
        var x = this.currentX - this.graph.panDx;
        var y = this.currentY - this.graph.panDy;

        this.x = Math.min(this.first.x, x);
        this.y = Math.min(this.first.y, y);
        this.width = Math.max(this.first.x, x) - this.x;
        this.height = Math.max(this.first.y, y) - this.y;

        var dx = (mxClient.IS_VML) ? this.graph.panDx : 0;
        var dy = (mxClient.IS_VML) ? this.graph.panDy : 0;

        this.div.style.left = (this.x + dx) + 'px';
        this.div.style.top = (this.y + dy) + 'px';
        this.div.style.width = Math.max(1, this.width) + 'px';
        this.div.style.height = Math.max(1, this.height) + 'px';
    }
};

/**
 * Function: destroy
 *
 * Destroys the handler and all its resources and DOM nodes. This does
 * normally not need to be called, it is called automatically when the
 * window unloads.
 */
mxRubberband.prototype.destroy = function () {
    if (!this.destroyed) {
        this.destroyed = true;
        this.graph.removeMouseListener(this);
        this.graph.removeListener(this.forceRubberbandHandler);
        this.graph.removeListener(this.panHandler);
        this.reset();

        if (this.sharedDiv != null) {
            this.sharedDiv = null;
        }
    }
};